// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/cmd_buffer_engine.h"
#include "gpu/command_buffer/service/common_decoder.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace gpu {

TEST(CommonDecoderBucket, Basic)
{
    CommonDecoder::Bucket bucket;
    EXPECT_EQ(0u, bucket.size());
    EXPECT_TRUE(NULL == bucket.GetData(0, 0));
}

TEST(CommonDecoderBucket, Size)
{
    CommonDecoder::Bucket bucket;
    bucket.SetSize(24);
    EXPECT_EQ(24u, bucket.size());
    bucket.SetSize(12);
    EXPECT_EQ(12u, bucket.size());
}

TEST(CommonDecoderBucket, GetData)
{
    CommonDecoder::Bucket bucket;

    bucket.SetSize(24);
    EXPECT_TRUE(NULL != bucket.GetData(0, 0));
    EXPECT_TRUE(NULL != bucket.GetData(24, 0));
    EXPECT_TRUE(NULL == bucket.GetData(25, 0));
    EXPECT_TRUE(NULL != bucket.GetData(0, 24));
    EXPECT_TRUE(NULL == bucket.GetData(0, 25));
    bucket.SetSize(23);
    EXPECT_TRUE(NULL == bucket.GetData(0, 24));
}

TEST(CommonDecoderBucket, SetData)
{
    CommonDecoder::Bucket bucket;
    static const char data[] = "testing";

    bucket.SetSize(10);
    EXPECT_TRUE(bucket.SetData(data, 0, sizeof(data)));
    EXPECT_EQ(0, memcmp(data, bucket.GetData(0, sizeof(data)), sizeof(data)));
    EXPECT_TRUE(bucket.SetData(data, 2, sizeof(data)));
    EXPECT_EQ(0, memcmp(data, bucket.GetData(2, sizeof(data)), sizeof(data)));
    EXPECT_FALSE(bucket.SetData(data, 0, sizeof(data) * 2));
    EXPECT_FALSE(bucket.SetData(data, 5, sizeof(data)));
}

class TestCommonDecoder : public CommonDecoder {
public:
    // Overridden from AsyncAPIInterface
    const char* GetCommandName(unsigned int command_id) const override
    {
        return GetCommonCommandName(static_cast<cmd::CommandId>(command_id));
    }

    // Overridden from AsyncAPIInterface
    error::Error DoCommand(unsigned int command,
        unsigned int arg_count,
        const void* cmd_data) override
    {
        return DoCommonCommand(command, arg_count, cmd_data);
    }

    CommonDecoder::Bucket* GetBucket(uint32 id) const
    {
        return CommonDecoder::GetBucket(id);
    }
};

class MockCommandBufferEngine : public CommandBufferEngine {
public:
    static const int32 kStartValidShmId = 1;
    static const int32 kValidShmId = 2;
    static const int32 kInvalidShmId = 3;
    static const size_t kBufferSize = 1024;
    static const int32 kValidOffset = kBufferSize / 2;
    static const int32 kInvalidOffset = kBufferSize;

    MockCommandBufferEngine()
        : CommandBufferEngine()
        , token_()
        , get_offset_(0)
    {
        scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory());
        shared_memory->CreateAndMapAnonymous(kBufferSize);
        buffer_ = MakeBufferFromSharedMemory(shared_memory.Pass(), kBufferSize);
    }

    // Overridden from CommandBufferEngine.
    scoped_refptr<gpu::Buffer> GetSharedMemoryBuffer(int32 shm_id) override
    {
        if (IsValidSharedMemoryId(shm_id))
            return buffer_;
        return NULL;
    }

    template <typename T>
    T GetSharedMemoryAs(uint32 offset)
    {
        DCHECK_LT(offset, kBufferSize);
        int8* buffer_memory = static_cast<int8*>(buffer_->memory());
        return reinterpret_cast<T>(&buffer_memory[offset]);
    }

    int32 GetSharedMemoryOffset(const void* memory)
    {
        int8* buffer_memory = static_cast<int8*>(buffer_->memory());
        ptrdiff_t offset = static_cast<const int8*>(memory) - &buffer_memory[0];
        DCHECK_GE(offset, 0);
        DCHECK_LT(static_cast<size_t>(offset), kBufferSize);
        return static_cast<int32>(offset);
    }

    // Overridden from CommandBufferEngine.
    void set_token(int32 token) override { token_ = token; }

    int32 token() const
    {
        return token_;
    }

    // Overridden from CommandBufferEngine.
    bool SetGetBuffer(int32 transfer_buffer_id) override
    {
        NOTREACHED();
        return false;
    }

    // Overridden from CommandBufferEngine.
    bool SetGetOffset(int32 offset) override
    {
        if (static_cast<size_t>(offset) < kBufferSize) {
            get_offset_ = offset;
            return true;
        }
        return false;
    }

    // Overridden from CommandBufferEngine.
    int32 GetGetOffset() override { return get_offset_; }

private:
    bool IsValidSharedMemoryId(int32 shm_id)
    {
        return shm_id == kValidShmId || shm_id == kStartValidShmId;
    }

    scoped_refptr<gpu::Buffer> buffer_;
    int32 token_;
    int32 get_offset_;
};

const int32 MockCommandBufferEngine::kStartValidShmId;
const int32 MockCommandBufferEngine::kValidShmId;
const int32 MockCommandBufferEngine::kInvalidShmId;
const size_t MockCommandBufferEngine::kBufferSize;
const int32 MockCommandBufferEngine::kValidOffset;
const int32 MockCommandBufferEngine::kInvalidOffset;

class CommonDecoderTest : public testing::Test {
protected:
    void SetUp() override { decoder_.set_engine(&engine_); }

    void TearDown() override { }

    template <typename T>
    error::Error ExecuteCmd(const T& cmd)
    {
        static_assert(T::kArgFlags == cmd::kFixed,
            "T::kArgFlags should equal cmd::kFixed");
        return decoder_.DoCommands(
            1, (const void*)&cmd, ComputeNumEntries(sizeof(cmd)), 0);
    }

    template <typename T>
    error::Error ExecuteImmediateCmd(const T& cmd, size_t data_size)
    {
        static_assert(T::kArgFlags == cmd::kAtLeastN,
            "T::kArgFlags should equal cmd::kAtLeastN");
        return decoder_.DoCommands(
            1, (const void*)&cmd, ComputeNumEntries(sizeof(cmd) + data_size), 0);
    }

    MockCommandBufferEngine engine_;
    TestCommonDecoder decoder_;
};

TEST_F(CommonDecoderTest, Initialize)
{
    EXPECT_EQ(0, engine_.GetGetOffset());
}

TEST_F(CommonDecoderTest, DoCommonCommandInvalidCommand)
{
    EXPECT_EQ(error::kUnknownCommand, decoder_.DoCommand(999999, 0, NULL));
}

TEST_F(CommonDecoderTest, HandleNoop)
{
    cmd::Noop cmd;
    const uint32 kSkipCount = 5;
    cmd.Init(kSkipCount);
    EXPECT_EQ(error::kNoError,
        ExecuteImmediateCmd(
            cmd, kSkipCount * kCommandBufferEntrySize));
    const uint32 kSkipCount2 = 1;
    cmd.Init(kSkipCount2);
    EXPECT_EQ(error::kNoError,
        ExecuteImmediateCmd(
            cmd, kSkipCount2 * kCommandBufferEntrySize));
}

TEST_F(CommonDecoderTest, SetToken)
{
    cmd::SetToken cmd;
    const int32 kTokenId = 123;
    EXPECT_EQ(0, engine_.token());
    cmd.Init(kTokenId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(kTokenId, engine_.token());
}

TEST_F(CommonDecoderTest, SetBucketSize)
{
    cmd::SetBucketSize cmd;
    const uint32 kBucketId = 123;
    const uint32 kBucketLength1 = 1234;
    const uint32 kBucketLength2 = 78;
    // Check the bucket does not exist.
    EXPECT_TRUE(NULL == decoder_.GetBucket(kBucketId));
    // Check we can create one.
    cmd.Init(kBucketId, kBucketLength1);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    CommonDecoder::Bucket* bucket;
    bucket = decoder_.GetBucket(kBucketId);
    EXPECT_TRUE(NULL != bucket);
    EXPECT_EQ(kBucketLength1, bucket->size());
    // Check we can change it.
    cmd.Init(kBucketId, kBucketLength2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    bucket = decoder_.GetBucket(kBucketId);
    EXPECT_TRUE(NULL != bucket);
    EXPECT_EQ(kBucketLength2, bucket->size());
    // Check we can delete it.
    cmd.Init(kBucketId, 0);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    bucket = decoder_.GetBucket(kBucketId);
    EXPECT_EQ(0u, bucket->size());
}

TEST_F(CommonDecoderTest, SetBucketData)
{
    cmd::SetBucketSize size_cmd;
    cmd::SetBucketData cmd;

    static const char kData[] = "1234567890123456789";

    const uint32 kBucketId = 123;
    const uint32 kInvalidBucketId = 124;

    size_cmd.Init(kBucketId, sizeof(kData));
    EXPECT_EQ(error::kNoError, ExecuteCmd(size_cmd));
    CommonDecoder::Bucket* bucket = decoder_.GetBucket(kBucketId);
    // Check the data is not there.
    EXPECT_NE(0, memcmp(bucket->GetData(0, sizeof(kData)), kData, sizeof(kData)));

    // Check we can set it.
    const uint32 kSomeOffsetInSharedMemory = 50;
    void* memory = engine_.GetSharedMemoryAs<void*>(kSomeOffsetInSharedMemory);
    memcpy(memory, kData, sizeof(kData));
    cmd.Init(kBucketId, 0, sizeof(kData),
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(0, memcmp(bucket->GetData(0, sizeof(kData)), kData, sizeof(kData)));

    // Check we can set it partially.
    static const char kData2[] = "ABCEDFG";
    const uint32 kSomeOffsetInBucket = 5;
    memcpy(memory, kData2, sizeof(kData2));
    cmd.Init(kBucketId, kSomeOffsetInBucket, sizeof(kData2),
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(0, memcmp(bucket->GetData(kSomeOffsetInBucket, sizeof(kData2)), kData2, sizeof(kData2)));
    const char* bucket_data = bucket->GetDataAs<const char*>(0, sizeof(kData));
    // Check that nothing was affected outside of updated area.
    EXPECT_EQ(kData[kSomeOffsetInBucket - 1],
        bucket_data[kSomeOffsetInBucket - 1]);
    EXPECT_EQ(kData[kSomeOffsetInBucket + sizeof(kData2)],
        bucket_data[kSomeOffsetInBucket + sizeof(kData2)]);

    // Check that it fails if the bucket_id is invalid
    cmd.Init(kInvalidBucketId, kSomeOffsetInBucket, sizeof(kData2),
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

    // Check that it fails if the offset is out of range.
    cmd.Init(kBucketId, bucket->size(), 1,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

    // Check that it fails if the size is out of range.
    cmd.Init(kBucketId, 0, bucket->size() + 1,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}

TEST_F(CommonDecoderTest, SetBucketDataImmediate)
{
    cmd::SetBucketSize size_cmd;
    int8 buffer[1024];
    cmd::SetBucketDataImmediate& cmd = *reinterpret_cast<cmd::SetBucketDataImmediate*>(&buffer);

    static const char kData[] = "1234567890123456789";

    const uint32 kBucketId = 123;
    const uint32 kInvalidBucketId = 124;

    size_cmd.Init(kBucketId, sizeof(kData));
    EXPECT_EQ(error::kNoError, ExecuteCmd(size_cmd));
    CommonDecoder::Bucket* bucket = decoder_.GetBucket(kBucketId);
    // Check the data is not there.
    EXPECT_NE(0, memcmp(bucket->GetData(0, sizeof(kData)), kData, sizeof(kData)));

    // Check we can set it.
    void* memory = &buffer[0] + sizeof(cmd);
    memcpy(memory, kData, sizeof(kData));
    cmd.Init(kBucketId, 0, sizeof(kData));
    EXPECT_EQ(error::kNoError,
        ExecuteImmediateCmd(cmd, sizeof(kData)));
    EXPECT_EQ(0, memcmp(bucket->GetData(0, sizeof(kData)), kData, sizeof(kData)));

    // Check we can set it partially.
    static const char kData2[] = "ABCEDFG";
    const uint32 kSomeOffsetInBucket = 5;
    memcpy(memory, kData2, sizeof(kData2));
    cmd.Init(kBucketId, kSomeOffsetInBucket, sizeof(kData2));
    EXPECT_EQ(error::kNoError,
        ExecuteImmediateCmd(cmd, sizeof(kData2)));
    EXPECT_EQ(0, memcmp(bucket->GetData(kSomeOffsetInBucket, sizeof(kData2)), kData2, sizeof(kData2)));
    const char* bucket_data = bucket->GetDataAs<const char*>(0, sizeof(kData));
    // Check that nothing was affected outside of updated area.
    EXPECT_EQ(kData[kSomeOffsetInBucket - 1],
        bucket_data[kSomeOffsetInBucket - 1]);
    EXPECT_EQ(kData[kSomeOffsetInBucket + sizeof(kData2)],
        bucket_data[kSomeOffsetInBucket + sizeof(kData2)]);

    // Check that it fails if the bucket_id is invalid
    cmd.Init(kInvalidBucketId, kSomeOffsetInBucket, sizeof(kData2));
    EXPECT_NE(error::kNoError,
        ExecuteImmediateCmd(cmd, sizeof(kData2)));

    // Check that it fails if the offset is out of range.
    cmd.Init(kBucketId, bucket->size(), 1);
    EXPECT_NE(error::kNoError,
        ExecuteImmediateCmd(cmd, sizeof(kData2)));

    // Check that it fails if the size is out of range.
    cmd.Init(kBucketId, 0, bucket->size() + 1);
    EXPECT_NE(error::kNoError,
        ExecuteImmediateCmd(cmd, sizeof(kData2)));
}

TEST_F(CommonDecoderTest, GetBucketStart)
{
    cmd::SetBucketSize size_cmd;
    cmd::SetBucketData set_cmd;
    cmd::GetBucketStart cmd;

    static const char kData[] = "1234567890123456789";
    static const char zero[sizeof(kData)] = {
        0,
    };

    const uint32 kBucketSize = sizeof(kData);
    const uint32 kBucketId = 123;
    const uint32 kInvalidBucketId = 124;

    // Put data in the bucket.
    size_cmd.Init(kBucketId, sizeof(kData));
    EXPECT_EQ(error::kNoError, ExecuteCmd(size_cmd));
    const uint32 kSomeOffsetInSharedMemory = 50;
    uint8* start = engine_.GetSharedMemoryAs<uint8*>(kSomeOffsetInSharedMemory);
    memcpy(start, kData, sizeof(kData));
    set_cmd.Init(kBucketId, 0, sizeof(kData),
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_EQ(error::kNoError, ExecuteCmd(set_cmd));

    // Check that the size is correct with no data buffer.
    uint32* memory = engine_.GetSharedMemoryAs<uint32*>(kSomeOffsetInSharedMemory);
    *memory = 0x0;
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        0, 0, 0);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(kBucketSize, *memory);

    // Check that the data is copied with data buffer.
    const uint32 kDataOffsetInSharedMemory = 54;
    uint8* data = engine_.GetSharedMemoryAs<uint8*>(kDataOffsetInSharedMemory);
    *memory = 0x0;
    memset(data, 0, sizeof(kData));
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        kBucketSize, MockCommandBufferEngine::kValidShmId,
        kDataOffsetInSharedMemory);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(kBucketSize, *memory);
    EXPECT_EQ(0, memcmp(data, kData, kBucketSize));

    // Check that we can get a piece.
    *memory = 0x0;
    memset(data, 0, sizeof(kData));
    const uint32 kPieceSize = kBucketSize / 2;
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        kPieceSize, MockCommandBufferEngine::kValidShmId,
        kDataOffsetInSharedMemory);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(kBucketSize, *memory);
    EXPECT_EQ(0, memcmp(data, kData, kPieceSize));
    EXPECT_EQ(0, memcmp(data + kPieceSize, zero, sizeof(kData) - kPieceSize));

    // Check that it fails if the result_id is invalid
    cmd.Init(kInvalidBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        0, 0, 0);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

    // Check that it fails if the data_id is invalid
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        1, MockCommandBufferEngine::kInvalidShmId, 0);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

    // Check that it fails if the data_size is invalid
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        1, 0, 0);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        MockCommandBufferEngine::kBufferSize + 1,
        MockCommandBufferEngine::kValidShmId, 0);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

    // Check that it fails if the data_offset is invalid
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        0, 0, 1);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        MockCommandBufferEngine::kBufferSize,
        MockCommandBufferEngine::kValidShmId, 1);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

    // Check that it fails if the result size is not set to zero
    *memory = 0x1;
    cmd.Init(kBucketId,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory,
        0, 0, 0);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}

TEST_F(CommonDecoderTest, GetBucketData)
{
    cmd::SetBucketSize size_cmd;
    cmd::SetBucketData set_cmd;
    cmd::GetBucketData cmd;

    static const char kData[] = "1234567890123456789";
    static const char zero[sizeof(kData)] = {
        0,
    };

    const uint32 kBucketId = 123;
    const uint32 kInvalidBucketId = 124;

    size_cmd.Init(kBucketId, sizeof(kData));
    EXPECT_EQ(error::kNoError, ExecuteCmd(size_cmd));
    const uint32 kSomeOffsetInSharedMemory = 50;
    uint8* memory = engine_.GetSharedMemoryAs<uint8*>(kSomeOffsetInSharedMemory);
    memcpy(memory, kData, sizeof(kData));
    set_cmd.Init(kBucketId, 0, sizeof(kData),
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_EQ(error::kNoError, ExecuteCmd(set_cmd));

    // Check we can get the whole thing.
    memset(memory, 0, sizeof(kData));
    cmd.Init(kBucketId, 0, sizeof(kData),
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(0, memcmp(memory, kData, sizeof(kData)));

    // Check we can get a piece.
    const uint32 kSomeOffsetInBucket = 5;
    const uint32 kLengthOfPiece = 6;
    const uint8 kSentinel = 0xff;
    memset(memory, 0, sizeof(kData));
    memory[-1] = kSentinel;
    cmd.Init(kBucketId, kSomeOffsetInBucket, kLengthOfPiece,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(0, memcmp(memory, kData + kSomeOffsetInBucket, kLengthOfPiece));
    EXPECT_EQ(0, memcmp(memory + kLengthOfPiece, zero, sizeof(kData) - kLengthOfPiece));
    EXPECT_EQ(kSentinel, memory[-1]);

    // Check that it fails if the bucket_id is invalid
    cmd.Init(kInvalidBucketId, kSomeOffsetInBucket, sizeof(kData),
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

    // Check that it fails if the offset is invalid
    cmd.Init(kBucketId, sizeof(kData) + 1, 1,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

    // Check that it fails if the size is invalid
    cmd.Init(kBucketId, 0, sizeof(kData) + 1,
        MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
    EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}

} // namespace gpu
